﻿using DAL;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Models;
using System.IO.Ports;
using System.Web;
using System.Threading;
using System.Diagnostics;
using System.Reflection;
using System.IO;
using System.Web.UI.WebControls;
using System.Security.Permissions;
using Sunny.UI;

namespace JY61PMCU
{

    #region 声明委托

    //更新角速度、速度、角度显示
    public delegate void ShowMsgDelegate(byte[] buffer);

    //添加log信息的委托，后两个参数分别为是否报警，以及是触发信号还是消除信号
    public delegate void AddLogInfoDelegate(int index, string logInfo, bool isAlarm, bool isAck);

    #endregion


    public partial class MainFrm : Form
    {

        #region 私有字段以及静态变量

        //创建对象
        private AlarmDataService alarmDataService = new AlarmDataService();

        //创建委托
        public AddLogInfoDelegate myLogInfoDelegate;

        //创建通信对象
        private JY61P jY61P = new JY61P();

        //创建线程执行信号源
        private CancellationTokenSource cts = new CancellationTokenSource();

        //创建串口参数实例
        private Serial serial = null;

        //创建配置文件读取路径
        private string settingPath = Application.StartupPath + "\\settings.ini";

        //系统设置页设置
        private Settings objSettings = null;

        //串口号的全局变量
        private string[] strPorts = null;

        //存储查询到的datatable
        DataTable dt = null;

        //创建解析参数实例，静态对象，方便多处使用
        private static JY61PParam jY61PParam = new JY61PParam();

        //实时监控状态
        private bool isAlarming = false;

        //连接状态
        private bool isOpen = false;
        public bool IsOpen
        {
            get { return isOpen; }

            set
            {
                isOpen = value;

                if (value == false)
                {
                    this.btn_Connect.Text = "     建立连接";
                    this.btn_Connect.Image = Properties.Resources.连接建立_未连接_;

                    this.tssl_ConnectStatus.Text = "未连接！";
                    this.tssl_ConnectStatus.ForeColor = Color.Red;
                }
                else
                {
                    this.btn_Connect.Text = "     已连接";
                    this.btn_Connect.Image = Properties.Resources.连接建立__已连接_;

                    this.tssl_ConnectStatus.Text = "已连接！";
                    this.tssl_ConnectStatus.ForeColor = Color.Black;
                }
            }
        }

        //自动更新定时器--ui更新队列
        private System.Windows.Forms.Timer updateTimer = null;

        //记录的定时器--单独线程
        private System.Timers.Timer recodingTimer = null;

        #region 自建心跳包

        private int heartBeat = 0;//即时心跳

        private int lastHearBeat = 0;//上一次的心跳

        private readonly int maxHeartBeatDiff = 10;//最大的心跳差距，超出该值则断定心跳停止，该值一定为正数

        private int heartBeatRescue = 0;//心跳抢救--给予心跳复苏一定时间

        #endregion

        //定义单位
        private const string AccelerationUnit = "g";
        private const string AngularVelocityUnit = "°/s";
        private const string AngleUnit = "°";
        private const string TempUnit = "°C";

        #endregion

        public MainFrm()
        {
            InitializeComponent();

            //绑定委托
            myLogInfoDelegate += AddLogInfo;

            //自动更新界面的timer
            updateTimer = new System.Windows.Forms.Timer();//实时更新
            updateTimer.Interval = 50;//更新间隔
            updateTimer.Tick += UpdateTimer_Tick;
            updateTimer.Enabled = true;//启动定时器

            //记录的定时器

            recodingTimer = new System.Timers.Timer();
            recodingTimer.Interval = 50;
            recodingTimer.Elapsed += RecodingTimer_Elapsed;
            recodingTimer.AutoReset = true;//可重复引发事件

            InitSetting();

        }

        #region 更新以及记录的Timer事件

        //自动记录事件
        private void RecodingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {

        }

        //更新事件-时间以及各界面内容显示
        private void UpdateTimer_Tick(object sender, EventArgs e)
        {
            //更新界面系统时间
            this.tssl_SysDate.Text = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");

            //判断心跳是否跳动,并且给予恢复时间
            if ((lastHearBeat - heartBeat) < 0)
            {
                lastHearBeat = heartBeat;//获取上次心跳

                //一旦获取心跳，重置 heartBeatRescue
                heartBeatRescue = 0;
            }

            else
            {
                #region 这里有个很重要的bug
                //if (delydeath < maxHeartBeatDiff )//观察是否能够恢复--error

                /***原因：
                 * 由于在关闭连接的时候，这条判断语句在逻辑上依然会执行，会导致 heartBeatRescue 自增到 maxHeartBeatDiff ，然后问题就来了
                 * 在重启链接的第一时刻，lastHearBeat = heartBeat 是一定的，致使会走到 else语句，而本应给予时间等到心跳的 heartBeatRescue 已经到到 maxHeartBeatDiff
                 * 语句会直接走到 IsOpen = false 界面就不再更新了，整个串口逻辑上就false了，但是实际还是在执行， 此时 heartBeat 在接收信息的增加下到了一定值，因此重新点击一次就会重启串口
                */
                #endregion

                if (IsOpen == true)//等待心跳复苏-->且必须在串口打开的情况下等待心跳复苏才有意义！！！

                {
                    if (heartBeatRescue <= maxHeartBeatDiff)
                    {
                        heartBeatRescue++;
                    }
                    else
                    {
                        IsOpen = false;
                        jY61P.DisConnect();
                        AddLogInfo(1,"已自动断开连接！");
                        //终止实时报警检测
                        KillAlarm();

                        //清空实时报警监控信息
                        this.listv_RealTimeAlarm.Items.Clear();
                    }                     
                }                                    
            }

            //更新界面信息
            if (IsOpen == true)
            {
                switch (this.tabc_Main.SelectedIndex)
                {
                    case 0:
                        //更新加速度
                        this.txt_AccX.Text = jY61PParam.AccelerationX.ToString("f2") + AccelerationUnit;//"f2"指定格式保留几位
                        this.txt_AccY.Text = jY61PParam.AccelerationY.ToString("f2") + AccelerationUnit;
                        this.txt_AccZ.Text = jY61PParam.AccelerationZ.ToString("f2") + AccelerationUnit;
                        this.txt_AccTemp.Text = jY61PParam.Temp.ToString("f2") + TempUnit;//温度

                        //更新角速度
                        this.txt_AnvX.Text = jY61PParam.AngularVelocityX.ToString("f2") + AngularVelocityUnit;
                        this.txt_AnvY.Text = jY61PParam.AngularVelocityY.ToString("f2") + AngularVelocityUnit;
                        this.txt_AnvZ.Text = jY61PParam.AngularVelocityZ.ToString("f2") + AngularVelocityUnit;
                        this.txt_AnvA.Text = ((jY61PParam.AngularVelocityX + jY61PParam.AngularVelocityY + jY61PParam.AngularVelocityZ) / 3).ToString("f2") + AngularVelocityUnit;

                        //更新角度
                        this.txt_AnX.Text = jY61PParam.AngleX.ToString("f2") + AngleUnit;
                        this.txt_AnY.Text = jY61PParam.AngleY.ToString("f2") + AngleUnit;
                        this.txt_AnZ.Text = jY61PParam.AngleZ.ToString("f2") + AngleUnit;
                        this.txt_AnA.Text = ((jY61PParam.AngleX + jY61PParam.AngleY + jY61PParam.AngleZ) / 3).ToString("f2") + AngleUnit;

                        break;

                    case 1:
                        //添加速度曲线
                        this.strpchart_Acc.PlotSingle(new double[] { jY61PParam.AccelerationX, jY61PParam.AccelerationY, jY61PParam.AccelerationZ });
                        break;

                    case 2:
                        //角速度曲线
                        this.strpchart_AnV.PlotSingle(new double[] { jY61PParam.AngularVelocityX, jY61PParam.AngularVelocityY, jY61PParam.AngularVelocityZ });
                        break;
                    case 3:
                        //角度曲线
                        this.strpchart_An.PlotSingle(new double[] { jY61PParam.AngleX, jY61PParam.AngleY, jY61PParam.AngleZ });
                        break;
                    default:
                        break;
                }
            }
        }

        #endregion


        #region 绑定下拉框及自动连接等初始化设置
        private void InitSetting()
        {
            IsOpen = false;//解决初始化链接按钮没有图片的bug

            #region 首页设置

            //动态显示listv第二列的长度
            this.listv_LogInfo.Columns[1].Width = this.listv_LogInfo.ClientSize.Width - this.listv_LogInfo.Columns[0].Width - 25;
            this.listv_RealTimeAlarm.Columns[1].Width = this.listv_RealTimeAlarm.ClientSize.Width - this.listv_RealTimeAlarm.Columns[0].Width - 25;

            //关闭自动生成列
            this.dgv_AlarmHistry.AutoGenerateColumns = false;

            //获取端口号
            string[] strPortNames = SerialPort.GetPortNames();


            //端口号
            if (strPortNames.Length > 0)
            {
                strPorts = strPortNames;//为全局变量赋值

                //初始化绑定串口
                this.cmb_PortName.DataSource = this.grp_cmbPortName.DataSource = strPortNames;
                this.cmb_PortName.SelectedIndex = this.grp_cmbPortName.SelectedIndex = 0;
                this.cmb_PortName.Select();

                #region 自动更新串口信息--多线程实现（目前关闭，因为可能比较耗费性能）
                /*
                //开辟新线程动态更新串口信息
                Task taskRefleshPort = Task.Run(new Action(() =>
                {
                    while (true)
                    {
                        string[] tmp = SerialPort.GetPortNames();

                        if (!tmp.SequenceEqual(strPorts) && isOpen == false)
                        {
                            strPorts = tmp;//保存新的串口信息
                            this.cmb_PortName.Invoke(new Action(() =>
                            {
                                this.cmb_PortName.DataSource = this.grp_cmbPortName.DataSource = strPorts;
                                this.cmb_PortName.SelectedIndex = this.grp_cmbPortName.SelectedIndex = strPorts.Length - 1;
                                this.cmb_PortName.Select();
                                MessageBox.Show("端口信息更新！");
                            }));
                        }

                        Thread.Sleep(500);
                    }
                }));

                */

                #endregion
            }

            //波特率
            string[] strPortBaudRate = { "9600", "115200" };

            this.cmb_BaudRate.DataSource = strPortBaudRate;
            this.cmb_BaudRate.SelectedIndex = 0;

            #endregion

            #region 系统设置页设置--自动连接

            objSettings = GetSettings();

            //如果设置出现错误
            if (objSettings == null)
                return;

            //从ini文件读取配置信息更新系统设置
            else
            {
                //若包含此端口则进行自动连接
                if (strPortNames.Contains(objSettings.PortName))
                {
                    //基础信息配置
                    this.cmb_PortName.Text = objSettings.PortName;
                    this.cmb_BaudRate.Text = objSettings.BaudRate;

                    //建立委托关系
                    jY61P.myGetMsg += GetMsg;

                    //实例化
                    serial = new Serial()
                    {
                        PortName = this.cmb_PortName.Text.Trim(),
                        BaudRate = Convert.ToInt32(this.cmb_BaudRate.Text.Trim()),
                        StopBits = StopBits.One,
                        Check = Parity.None,
                        DataBits = 8
                    };

                    //打开串口
                    IsOpen = jY61P.Connection(serial);

                    //连接上了
                    if (jY61P.serialPort.IsOpen == true)
                    {
                        AddLogInfo(0, $"自动连接成功!  端口号：{this.cmb_PortName.Text},波特率:{this.cmb_BaudRate.Text}");

                        //开启报警监控
                        StartAlarm();
                    }
                    else
                    {
                        AddLogInfo(1, $"自动连接失败，{this.cmb_PortName.Text}拒绝访问！  端口号：{this.cmb_PortName.Text},波特率:{this.cmb_BaudRate.Text}");
                        return;
                    }
                }
                else
                {
                    AddLogInfo(1, $"自动连接失败，{objSettings.PortName}不存在！  端口号：{objSettings.PortName},波特率:{this.cmb_BaudRate.Text}");
                    return;
                }

            }

            #endregion

            #region 通过系统配置信息更新系统设置

            this.updateTimer.Interval = objSettings.UpdateCyc;//界面更新

            this.recodingTimer.Interval = objSettings.RecodingCyc;//记录更新

            //设置曲线
            this.strpchart_Acc.LegendVisible = this.strpchart_AnV.LegendVisible = this.strpchart_An.LegendVisible = objSettings.Legend;//是否显示legend

            this.strpchart_Acc.DisplayPoints = this.strpchart_AnV.DisplayPoints = this.strpchart_An.DisplayPoints = int.Parse(objSettings.Num);//横轴数量


            #endregion

        }
        #endregion


        #region 建立连接以及断开连接
        //建立连接
        private void btn_Connect_Click(object sender, EventArgs e)
        {
            //建立委托关系
            jY61P.myGetMsg += GetMsg;

            //实例化
            serial = new Serial()
            {
                PortName = this.cmb_PortName.Text.Trim(),
                BaudRate = Convert.ToInt32(this.cmb_BaudRate.Text.Trim()),
                StopBits = StopBits.One,
                Check = Parity.None,
                DataBits = 8
            };

            //打开串口
            IsOpen = jY61P.Connection(serial);

            //连接上了
            if (IsOpen == true)
            {
                //重置心跳
                heartBeatRescue = 0;

                AddLogInfo(0, $"手动连接成功!  端口号：{this.cmb_PortName.Text},波特率:{this.cmb_BaudRate.Text}");
            }
            else
            {
                AddLogInfo(2, $"手动连接失败!  端口号：{this.cmb_PortName.Text},波特率:{this.cmb_BaudRate.Text}");
                return;
            }


            //启动实时报警信息
            StartAlarm();

        }

        //断开连接
        private void btn_DisConnect_Click(object sender, EventArgs e)
        {
            try
            {
                //关闭串口
                IsOpen = !jY61P.DisConnect();

                //终止实时报警检测
                KillAlarm();

                //清空实时报警监控信息
                this.listv_RealTimeAlarm.Items.Clear();

                AddLogInfo(0, $"手动断开连接成功!  端口号：{this.cmb_PortName.Text},波特率:{this.cmb_BaudRate.Text}");
            }
            catch (Exception ex)
            {

                AddLogInfo(2, $"手动断开连接失败!  端口号：{this.cmb_PortName.Text},波特率:{this.cmb_BaudRate.Text}");
                throw ex;
            }

        }

        #endregion


        #region 配置信息的保存以及系统重启
        //读取配置信息
        private Settings GetSettings()
        {
            Settings objSettings = new Settings();

            try
            {
                //系统参数
                objSettings.PortName = INIConfigHelper.ReadString("系统参数", "PortName", "", settingPath);
                objSettings.BaudRate = INIConfigHelper.ReadString("系统参数", "BaudRate", "", settingPath);
                objSettings.UpdateCyc = INIConfigHelper.ReadInteger("系统参数", "UpdateCyc", 50, settingPath);
                objSettings.RecodingCyc = INIConfigHelper.ReadInteger("系统参数", "RecodingCyc", 50, settingPath);
                objSettings.Num = INIConfigHelper.ReadString("系统参数", "Num", "1000", settingPath);
                objSettings.Legend = INIConfigHelper.ReadBoolean("系统参数", "Legend", true, settingPath);

                //X轴
                objSettings.XMax = INIConfigHelper.ReadInteger("X轴报警", "XMax", 180, settingPath);
                objSettings.XMin = INIConfigHelper.ReadInteger("X轴报警", "XMin", -180, settingPath);
                objSettings.XAlarm = INIConfigHelper.ReadBoolean("X轴报警", "XAlarm", false, settingPath);

                //Y轴
                objSettings.YMax = INIConfigHelper.ReadInteger("Y轴报警", "YMax", 180, settingPath);
                objSettings.YMin = INIConfigHelper.ReadInteger("Y轴报警", "YMin", -180, settingPath);
                objSettings.YAlarm = INIConfigHelper.ReadBoolean("Y轴报警", "YAlarm", false, settingPath);

                //Z轴
                objSettings.ZMax = INIConfigHelper.ReadInteger("Z轴报警", "ZMax", 180, settingPath);
                objSettings.ZMin = INIConfigHelper.ReadInteger("Z轴报警", "ZMin", -180, settingPath);
                objSettings.ZAlarm = INIConfigHelper.ReadBoolean("Z轴报警", "ZAlarm", false, settingPath);

                return objSettings;
            }
            catch (Exception ex)
            {

                AddLogInfo(3, "读取配置文件失败!" + ex.Message);
                return null;
            }
        }

        //更新系统配置设置
        private void tabc_Main_SelectedIndexChanged(object sender, EventArgs e)
        {

            if (this.tabc_Main.SelectedIndex == 5 && this.tabc_Details.SelectedIndex == 0)//如果信息没保存的话，瞎点然后切出去再切回来要重置
            {
                //系统配置
                this.grp_cmbPortName.Text = objSettings.PortName;
                this.grp_cmbBaudRates.Text = objSettings.BaudRate;
                this.grp_num_UpdateCyc.Value = objSettings.UpdateCyc;
                this.grp_num_RecodingCyc.Value = objSettings.RecodingCyc;
                this.grp_txtNum.Text = objSettings.Num;
                this.grp_ckb_Legend.Checked = objSettings.Legend;

                //三轴设置
                this.grp_txt_XMax.Text = objSettings.XMax.ToString();
                this.grp_txt_XMin.Text = objSettings.XMin.ToString();
                this.grp_ckb_XAlarm.Checked = objSettings.XAlarm;

                this.grp_txt_YMax.Text = objSettings.YMax.ToString();
                this.grp_txt_Ymin.Text = objSettings.YMin.ToString();
                this.grp_ckb_YAlarm.Checked = objSettings.YAlarm;

                this.grp_txt_ZMax.Text = objSettings.ZMax.ToString();
                this.grp_txt_ZMin.Text = objSettings.ZMin.ToString();
                this.grp_ckb_ZAlarm.Checked = objSettings.ZAlarm;
            }
        }

        //同上
        private void tabc_Details_SelectedIndexChanged(object sender, EventArgs e)
        {

            if (this.tabc_Details.SelectedIndex == 0)
            {
                //系统配置
                this.grp_cmbPortName.Text = objSettings.PortName;
                this.grp_cmbBaudRates.Text = objSettings.BaudRate;
                this.grp_num_UpdateCyc.Value = objSettings.UpdateCyc;
                this.grp_num_RecodingCyc.Value = objSettings.RecodingCyc;
                this.grp_txtNum.Text = objSettings.Num;
                this.grp_ckb_Legend.Checked = objSettings.Legend;

                //三轴设置
                this.grp_txt_XMax.Text = objSettings.XMax.ToString();
                this.grp_txt_XMin.Text = objSettings.XMin.ToString();
                this.grp_ckb_XAlarm.Checked = objSettings.XAlarm;

                this.grp_txt_YMax.Text = objSettings.YMax.ToString();
                this.grp_txt_Ymin.Text = objSettings.YMin.ToString();
                this.grp_ckb_YAlarm.Checked = objSettings.YAlarm;

                this.grp_txt_ZMax.Text = objSettings.ZMax.ToString();
                this.grp_txt_ZMin.Text = objSettings.ZMin.ToString();
                this.grp_ckb_ZAlarm.Checked = objSettings.ZAlarm;
            }
        }



        //保存系统设置
        private void btn_SaveSettings_Click(object sender, EventArgs e)
        {
            if (IntVerify(this.grp_txtNum.Text) && IntVerify(this.grp_txt_XMax.Text)
                && IntVerify(this.grp_txt_XMin.Text) && IntVerify(this.grp_txt_YMax.Text)
                && IntVerify(this.grp_txt_Ymin.Text) && IntVerify(this.grp_txt_ZMax.Text) && IntVerify(this.grp_txt_ZMin.Text))
            {

                bool result = true;

                //系统参数
                result &= INIConfigHelper.WriteString("系统参数", "PortName", this.grp_cmbPortName.Text, settingPath);
                result &= INIConfigHelper.WriteString("系统参数", "BaudRate", this.grp_cmbBaudRates.Text, settingPath);
                result &= INIConfigHelper.WriteInteger("系统参数", "UpdateCyc", (int)this.grp_num_UpdateCyc.Value, settingPath);
                result &= INIConfigHelper.WriteInteger("系统参数", "RecodingCyc", (int)this.grp_num_RecodingCyc.Value, settingPath);
                result &= INIConfigHelper.WriteString("系统参数", "Num", this.grp_txtNum.Text, settingPath);
                result &= INIConfigHelper.WriteBoolean("系统参数", "Legend", this.grp_ckb_Legend.Checked, settingPath);

                //X轴
                result &= INIConfigHelper.WriteString("X轴报警", "XMax", this.grp_txt_XMax.Text, settingPath);
                result &= INIConfigHelper.WriteString("X轴报警", "XMin", this.grp_txt_XMin.Text, settingPath);
                result &= INIConfigHelper.WriteBoolean("X轴报警", "XAlarm", grp_ckb_XAlarm.Checked, settingPath);

                //Y轴
                result &= INIConfigHelper.WriteString("Y轴报警", "YMax", this.grp_txt_YMax.Text, settingPath);
                result &= INIConfigHelper.WriteString("Y轴报警", "YMin", this.grp_txt_Ymin.Text, settingPath);
                result &= INIConfigHelper.WriteBoolean("Y轴报警", "YAlarm", grp_ckb_YAlarm.Checked, settingPath);

                //Z轴
                result &= INIConfigHelper.WriteString("Z轴报警", "ZMax", this.grp_txt_ZMax.Text, settingPath);
                result &= INIConfigHelper.WriteString("Z轴报警", "ZMin", this.grp_txt_ZMin.Text, settingPath);
                result &= INIConfigHelper.WriteBoolean("Z轴报警", "ZAlarm", grp_ckb_ZAlarm.Checked, settingPath);

                if (result == true)//正确则立即应用某些可即时应用的设置
                {
                    //若为真则更新全局变量objsettings
                    objSettings = this.GetSettings();

                    if (objSettings == null)
                    {
                        AddLogInfo(1, "配置保存并即时应用失败！");
                        return;
                    }
                    this.updateTimer.Interval = objSettings.UpdateCyc;//界面更新

                    this.recodingTimer.Interval = objSettings.RecodingCyc;//记录更新

                    //设置曲线legend是否可见
                    this.strpchart_Acc.LegendVisible = this.strpchart_AnV.LegendVisible = this.strpchart_An.LegendVisible = objSettings.Legend;//是否显示legend

                    AddLogInfo(0, "配置保存并即时应用成功！");
                }
                else
                {
                    AddLogInfo(1, "配置保存失败！");
                }
            }
            else
            {
                AddLogInfo(1, "配置格式不正确！");
                return;
            }
        }


        //验证配置数据正确性的函数 string-->int
        private bool IntVerify(string str)
        {
            return int.TryParse(str, out int result);
        }


        //重启系统
        private void btn_ReStartSys_Click(object sender, EventArgs e)
        {
            DialogResult dr = MessageBox.Show("是否重启系统？", "系统重启", MessageBoxButtons.OKCancel, MessageBoxIcon.Question);

            if (dr == DialogResult.OK)
            {
                Application.Exit();

                Thread.Sleep(500);

                // 启动新实例并传递重启参数
                Process.Start(new ProcessStartInfo
                {
                    FileName = Assembly.GetExecutingAssembly().Location,
                    Arguments = "--restart",
                    UseShellExecute = true
                });
            }
        }

        #endregion


        #region 数据解析以及数据解析函数

        /// <summary>
        /// 解析数据的函数
        /// </summary>
        /// <param name="buffer">待解析字节数组</param>
        /// <param name="offset">包头相对偏移量</param>
        public void AnalyzeData(byte[] buffer, int offset)
        {
            //判断报文包头索引
            if (buffer[0 + offset] == 0x55 && buffer[1 + offset] == 0x51 &&
                buffer[11 + offset] == 0x55 && buffer[12 + offset] == 0x52 &&
                buffer[22 + offset] == 0x55 && buffer[23 + offset] == 0x53)//标准格式
            {
                //判断校验和--（单独写一个函数）
                if (SumCheck(buffer, 0 + offset, 11) && SumCheck(buffer, 11 + offset, 11) && SumCheck(buffer, 22 + offset, 11))
                {
                    //建立心跳
                    heartBeat++;
                    if (heartBeat >= 10000)//避免心跳值过大-->可能出现负数
                        heartBeat = 0; lastHearBeat = 0;//必须同时也 lastHearBeat ，不然会出现逻辑错误--> 已证实：如果没有恢复时间 ，那么程序必然过一段时间就会自动断开

                    //加速度参数
                    jY61PParam.IAccelerationX = BitConverter.ToInt16(buffer, 2 + offset);
                    jY61PParam.IAccelerationY = BitConverter.ToInt16(buffer, 4 + offset);
                    jY61PParam.IAccelerationZ = BitConverter.ToInt16(buffer, 6 + offset);
                    jY61PParam.ITemp = BitConverter.ToInt16(buffer, 8 + offset);

                    //角速度参数
                    jY61PParam.IAngularVelocityX = BitConverter.ToInt16(buffer, 13 + offset);
                    jY61PParam.IAngularVelocityY = BitConverter.ToInt16(buffer, 15 + offset);
                    jY61PParam.IAngularVelocityZ = BitConverter.ToInt16(buffer, 17 + offset);
                    jY61PParam.IVolt = BitConverter.ToInt16(buffer, 19 + offset);

                    //角度参数
                    jY61PParam.IAngleX = BitConverter.ToInt16(buffer, 24 + offset);
                    jY61PParam.IAngleY = BitConverter.ToInt16(buffer, 26 + offset);
                    jY61PParam.IAngleZ = BitConverter.ToInt16(buffer, 28 + offset);
                    jY61PParam.IVersion = BitConverter.ToInt16(buffer, 30 + offset);


                }
            }

        }


        /// <summary>
        /// 数据解析
        /// </summary>
        /// <param name="buffer">待分析字节数据</param>
        public void GetMsg(byte[] buffer)
        {
            int offset = 0;//偏移量

            for (int i = 0; i < buffer.Length - 1; i++)//buffer.Length - 1数组最大下标
            {
                if (buffer[i] == 0x55 && buffer[i + 1] == 0x51)//找到“包头”
                {
                    offset = i;
                    if (buffer.Length - offset < 44)//不够一帧
                        return;
                    else
                    {
                        AnalyzeData(buffer, offset);

                        return;//必须加上，不然会对整个88个字节依次遍历--数据就不及时了，而且也会耗时
                    }
                }
            }
        }


        #region 数据和校验
        /// <summary>
        /// 数据和校验
        /// </summary>
        /// <param name="buffer">待校验报文</param>
        /// <param name="start">起始索引下标</param>
        /// <param name="length">分段截取长度</param>
        /// <returns></returns>
        private bool SumCheck(byte[] buffer, int start, int length)
        {
            //缓冲区不存在，或者无数据
            if (buffer == null || buffer.Length == 0)
                return false;
            //不符合规范的输入
            if (start < 0 || start > buffer.Length)
                return false;
            if (length <= 1)
                return false;
            if ((start + length) > buffer.Length)
                return false;

            byte sum = 0;

            //求和
            for (int i = start; i < start + length - 1; i++)
            {
                sum += buffer[i];
            }

            //判断
            if (sum == buffer[start + length - 1])
                return true;
            else
                return false;
        }

        #endregion

        #endregion


        #region 日志相关函数

        /// <summary>
        /// 添加日志信息
        /// </summary>
        /// <param name="index">日志提示图标索引</param>
        /// <param name="logInfo">具体日志信息</param>
        /// <param name="isAlarm">警报</param>
        /// <param name="isAck">触发或消除</param>
        private void AddLogInfo(int index, string logInfo, bool isAlarm = false, bool isAck = false)
        {
            //是否是跨线程调用都有最优解
            if (this.listv_LogInfo.InvokeRequired)
            {
                this.listv_LogInfo.Invoke(new Action(() =>
                {
                    AddLogInfoMethod(index, logInfo, isAlarm, isAck);
                }));
            }
            else
            {
                AddLogInfoMethod(index, logInfo, isAlarm, isAck);
            }
        }

        //添加日志的函数--包含首页日志添加以及报警日志添加
        private void AddLogInfoMethod(int index, string logInfo, bool isAlarm = false, bool isAck = false)
        {
            System.Windows.Forms.ListViewItem listItems = new System.Windows.Forms.ListViewItem(" " + DateTime.Now.ToString("HH:mm:ss"), index);//初始化listv的元素项

            //更改log信息
            string strLog = string.Empty;

            if (isAlarm)
            {
                strLog = isAck ? logInfo + "触发!" : logInfo + "消除！";
            }
            else
            {
                strLog = logInfo;
            }

            //向子项中添加一条数据，加入有很多列，那就依次添加
            listItems.SubItems.Add(strLog);

            //保证最新的日志在最上边(采用插入的方式,插入到0)
            this.listv_LogInfo.Items.Insert(0, listItems);

            //报警信息填入到报警日志界面
            if (isAlarm)
            {
                //触发
                if (isAck)
                {
                    listItems = new System.Windows.Forms.ListViewItem(" " + DateTime.Now.ToString("HH:mm:ss"), index);

                    listItems.SubItems.Add(strLog);


                    //已经有了就不加入
                    foreach (System.Windows.Forms.ListViewItem item in this.listv_RealTimeAlarm.Items)
                    {
                        if (item.SubItems[1].Text.Equals(strLog))
                            return;
                    }

                    this.listv_RealTimeAlarm.Items.Insert(0, listItems);

                }
                //消除
                else
                {
                    //遍历查找删除
                    foreach (System.Windows.Forms.ListViewItem item in this.listv_RealTimeAlarm.Items)
                    {
                        if (item.SubItems[1].Text.Equals(logInfo + "触发!"))
                        {
                            this.listv_RealTimeAlarm.Items.Remove(item);
                        }
                    }
                }
            }

            //报警信息写入数据库
            if (isAlarm)
            {
                //实例化对象并存储到数据库
                alarmDataService.InsertAlarmData(new AlarmData()
                {
                    AlarmTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
                    AlarmName = logInfo,
                    AlarmStatus = isAck ? "报警触发" : "报警消除"
                });
            }


            //保存日志信息
            LogHelper.WriteLog(logInfo);
        }

        #endregion


        #region 多线程实现实时报警监控


        //开启实时报警检测
        private void StartAlarm()
        {

            if (isAlarming == true)
                return;//不能重复启动

            //创建新的cts
            cts = new CancellationTokenSource();

            try
            {
                Task task = Task.Run(() => CheckAlarm(), cts.Token);
                isAlarming = true;//打开成功！

                AddLogInfo(0, "成功开启实时报警检测！");
            }
            catch (Exception ex)
            {

                MessageBox.Show("启动实时报警监控出现错误！" + ex);
                isAlarming = false;//打开失败！

                AddLogInfo(2, "开启实时报警检测失败！");

                //失败后传递关闭线程信息
                cts.Cancel();
                cts.Dispose();
            }
        }


        //终止实时报警检测
        private void KillAlarm()
        {
            //压根没有开启
            if (isAlarming == false)
                return;


            //已开启
            if (cts != null)//传递终结信号并释放资源
            {
                cts.Cancel();//释放cts
                cts.Dispose();
            }

            isAlarming = false;//修改标志位

            AddLogInfo(0, "实时报警检测已关闭！");

        }


        //实时报警检测
        private void CheckAlarm()
        {
            while (!cts.IsCancellationRequested)//未取消则一直执行
            {
                if (IsOpen == true)
                {

                    //**角度--X轴--报警
                    if (objSettings.XAlarm == true)
                    {
                        //当前值大于上限值，并且上一次值小于上限值则代表临时超限
                        if (jY61PParam.AngleX > objSettings.XMax && objSettings.AnCacheXMax < objSettings.XMax)
                            AddLogInfo(1, "X轴角度高限报警", true, true);//报警

                        if (jY61PParam.AngleX < objSettings.XMin && objSettings.AnCacheXMin > objSettings.XMin)
                            AddLogInfo(1, "X轴角度低限报警", true, true);//报警

                        //恢复--上一次值大于上限值，当前值小于上限值
                        if (objSettings.AnCacheXMax > objSettings.XMax && jY61PParam.AngleX < objSettings.XMax)
                            AddLogInfo(0, "X轴角度高限报警", true, false);//清除

                        if (objSettings.AnCacheXMin < objSettings.XMin && jY61PParam.AngleX > objSettings.XMin)
                            AddLogInfo(0, "X轴角度低限报警", true, false);//清除

                        //记录上次值
                        objSettings.AnCacheXMax = objSettings.AnCacheXMin = jY61PParam.AngleX;
                    }


                    //**角度--Y轴--报警
                    if (objSettings.YAlarm == true)
                    {
                        if (jY61PParam.AngleY > objSettings.YMax && objSettings.AnCacheYMax < objSettings.YMax)
                            AddLogInfo(1, "Y轴角度高限报警", true, true);

                        if (jY61PParam.AngleY < objSettings.YMin && objSettings.AnCacheYMin > objSettings.YMin)
                            AddLogInfo(1, "Y轴角度低限报警", true, true);

                        //恢复--上一次值大于上限值，当前值小于上限值
                        if (objSettings.AnCacheYMax > objSettings.YMax && jY61PParam.AngleY < objSettings.YMax)
                            AddLogInfo(0, "Y轴角度高限报警", true, false);

                        if (objSettings.AnCacheYMin < objSettings.YMin && jY61PParam.AngleY > objSettings.YMin)
                            AddLogInfo(0, "Y轴角度低限报警", true, false);

                        objSettings.AnCacheYMax = objSettings.AnCacheYMin = jY61PParam.AngleY;
                    }


                    //**角度--Z轴--报警
                    if (objSettings.ZAlarm == true)
                    {
                        if (jY61PParam.AngleZ > objSettings.ZMax && objSettings.AnCacheZMax < objSettings.ZMax)
                            AddLogInfo(1, "Z轴角度高限报警", true, true);

                        if (jY61PParam.AngleZ < objSettings.ZMin && objSettings.AnCacheZMin > objSettings.ZMin)
                            AddLogInfo(1, "Z轴角度低限报警", true, true);

                        //恢复--上一次值大于上限值，当前值小于上限值
                        if (objSettings.AnCacheZMax > objSettings.ZMax && jY61PParam.AngleZ < objSettings.ZMax)
                            AddLogInfo(0, "Z轴角度高限报警", true, false);

                        if (objSettings.AnCacheZMin < objSettings.ZMin && jY61PParam.AngleZ > objSettings.ZMin)
                            AddLogInfo(0, "Z轴角度低限报警", true, false);

                        objSettings.AnCacheZMax = objSettings.AnCacheZMin = jY61PParam.AngleZ;
                    }


                    Thread.Sleep(200);//避免报警频率过于频繁
                }

                if (isAlarming == false)
                {
                    //数据归位--报警解除时避免要重置cache，便于第一次重启
                    objSettings.AnCacheXMax = float.MinValue;
                    objSettings.AnCacheXMin = float.MaxValue;
                    objSettings.AnCacheYMax = float.MinValue;
                    objSettings.AnCacheYMin = float.MaxValue;
                    objSettings.AnCacheZMax = float.MinValue;
                    objSettings.AnCacheZMin = float.MaxValue;
                }
            }
        }


        #endregion


        #region 其他按钮功能(清空日志、帮助文档、查询以及导出报警日志信息)

        //双击清空
        private void lab_CleanLog_DoubleClick(object sender, EventArgs e)
        {
            this.listv_LogInfo.Items.Clear();
        }

        //打开帮助
        private void btn_Helper_Click(object sender, EventArgs e)
        {
            using (OpenFileDialog fileDialog = new OpenFileDialog())
            {
                fileDialog.Multiselect = false;//不允许多选

                string strPath = Application.StartupPath + "\\Helper";

                if (!Directory.Exists(strPath))//若不存在该文件夹则创建
                {
                    Directory.CreateDirectory(strPath);
                }

                fileDialog.InitialDirectory = strPath;//初始路径

                fileDialog.Title = "请选择要打开的帮助文档!";

                fileDialog.Filter = "pdf文件|(*.pdf)";

                //打开指定文件
                if (fileDialog.ShowDialog() == DialogResult.OK)
                {
                    Process.Start(fileDialog.FileName);
                }
            }
        }


        //查询历史报警日志
        private void btn_Query_Click(object sender, EventArgs e)
        {
            //获取时间
            DateTime start = this.dtp_StartTime.Value;
            DateTime end = this.dtp_EndTime.Value;

            if (start >= end)
            {
                MessageBox.Show("开始时间必须小于结束时间！");
                return;
            }

            //查询
            dt = alarmDataService.QueryAlarmData(start.ToString("yyyy-MM-dd HH:mm:ss"),
                                                                           end.ToString("yyyy-MM-dd HH:mm:ss"));

            if (dt != null)
            {
                this.dgv_AlarmHistry.DataSource = null;//先清除绑定避免出错
                this.dgv_AlarmHistry.DataSource = dt;

                //优化提醒色显示
                if (this.dgv_AlarmHistry.RowCount > 0)
                {
                    for (int i = 0; i < this.dgv_AlarmHistry.RowCount; i++)
                    {
                        if (this.dgv_AlarmHistry.Rows[i].Cells[2].Value.ToString() == "报警触发")
                        {
                            this.dgv_AlarmHistry.Rows[i].DefaultCellStyle.BackColor = Color.FromArgb(205, 115, 106);//淡红色

                            this.dgv_AlarmHistry.Rows[i].DefaultCellStyle.SelectionBackColor = Color.FromArgb(140, 41, 78);//暗红色
                            this.dgv_AlarmHistry.Rows[i].DefaultCellStyle.SelectionForeColor = Color.White;
                        }
                        else
                        {
                            this.dgv_AlarmHistry.Rows[i].DefaultCellStyle.BackColor = Color.FromArgb(164, 203, 168);//浅绿色
                            this.dgv_AlarmHistry.Rows[i].DefaultCellStyle.SelectionBackColor = Color.FromArgb(0, 80, 91);//暗绿色
                            this.dgv_AlarmHistry.Rows[i].DefaultCellStyle.SelectionForeColor = Color.White;
                        }
                    }
                }

            }
        }


        //导出报警日志--excel--nopy方式
        private void btn_Export_Click(object sender, EventArgs e)
        {
            //不符合数据要求的列表无法查询
            if (dt == null || dt.Rows.Count < 1)
            {
                MessageBox.Show("未找到相应的数据或相应数据为空！", "提示信息");
                return;
            }

            //实例化
            SaveFileDialog sfd = new SaveFileDialog()
            {
                Filter = "XLS文件(*.xls)|*.xls|所有文件|*.*",//文件筛选
                Title = "请选择要保存的文件路径",
                FileName = "JY61P历史报警",//路径以及文件名
                DefaultExt = "*.xls",//默认保存类型
                AddExtension = true,//自动添加扩展名         
            };


            if (sfd.ShowDialog() == DialogResult.OK)
            {
                if (NPOIHelper.Export(dt, "JY61P历史报警", sfd.FileName))
                {
                    AddLogInfo(0, "历史报警日志导出成功！");
                }
                else
                {
                    AddLogInfo(2, "历史报警日志导出失败！");
                }
            }
        }

        #endregion


        #region 其他细节优化

        //重绘dgv的行号
        private void dgv_AlarmHistry_RowPostPaint(object sender, DataGridViewRowPostPaintEventArgs e)
        {
            var grid = sender as DataGridView;
            var rowIdx = (e.RowIndex + 1).ToString();

            var centerFormat = new StringFormat()
            {
                // right alignment might actually make more sense for numbers
                Alignment = StringAlignment.Center,
                LineAlignment = StringAlignment.Center
            };

            var headerBounds = new Rectangle(e.RowBounds.Left, e.RowBounds.Top, grid.RowHeadersWidth, e.RowBounds.Height);
            e.Graphics.DrawString(rowIdx, this.Font, SystemBrushes.ControlText, headerBounds, centerFormat);
        }

        //关闭按钮
        private void btn_Close_Click(object sender, EventArgs e)
        {
            DialogResult ds = MessageBox.Show("确定关闭吗？", "关闭提示", MessageBoxButtons.OKCancel);
            if (ds == DialogResult.OK)
                //this.Close();
                Application.Exit();
            else
                return;
        }

        private void btn_Min_Click(object sender, EventArgs e)
        {
            this.WindowState = FormWindowState.Minimized;  //最小化
        }


        //通过窗体MouseDown、MouseMove、MouseUp事件实现窗体移动
        Point point; //鼠标按下时的点
        bool isMoving = false;//标识是否拖动

        private void Form1_MouseDown(object sender, MouseEventArgs e)
        {
            point = e.Location;//按下的点
            isMoving = true;//启动拖动
        }

        private void Form1_MouseMove(object sender, MouseEventArgs e)
        {
            if (e.Button == MouseButtons.Left && isMoving)
            {
                Point pNew = new Point(e.Location.X - point.X, e.Location.Y - point.Y);
                //Location = new Point(Location.X + pNew.X, Location.Y + pNew.Y);
                Location += new Size(pNew);
            }
        }

        private void Form1_MouseUp(object sender, MouseEventArgs e)
        {
            isMoving = false;//停止
        }


        //鼠标移动到btn上方时候发生的变化
        private void btn_Top_MouseEnter(object sender, EventArgs e)
        {
            if (sender is System.Windows.Forms.Button btn)
            {
                if (btn.Name == this.btn_Close.Name)
                    btn.ForeColor = Color.Red;
                if (btn.Name == this.btn_Min.Name)
                    btn.ForeColor = Color.White;
            }
        }

        private void btn_Top_MouseLeave(object sender, EventArgs e)
        {
            if (sender is System.Windows.Forms.Button btn)
            {
                btn.ForeColor = Color.Black;
            }
        }

        #endregion
    }
}
